Linux 下的 GPIO 编程
GPIO(General-purpose input/output) 通用型之输入输出的简称。在嵌入式系统中,经常需要控制许多结构简单的外部设备或者电路,这些设备有的需要通过 CPU 控制,有的需要 CPU 提供输入信号。并且,许多设备或电路只要求有开/关两种状态就够了,比如 LED 的亮与灭。对这些设备的控制,使用传统的串口或者并口就显得比较复杂,所以,在嵌入式微处理器上通常提供了一种“通用可编程 I/O 端口”,也就是 GPIO。
Linux 下的 GPIO 设备
配置 Pin MUX
通过配置寄存器可以定义 I/O 管脚功能,在使用 GPIO 功能时,首先要将相应的引脚配置为 GPIO,管脚寄存器和配置功能参考《PAD config registers》 手册。
下面以 J176 上的 SPI2_CSN0,SPI2_CLK,SPI2_MOSI,SPI2_MISO,SPI2_CSN1 做简单说明。
引脚功能定义:MUX_CFG_002 (address:0x3fe830334)
设置 SPI2_CLK 为GPIO0_34
# 读取 0x3fe830334 数值 root@thead-910:~# memtool md -l 0x3fe830334 3fe830334: 00005014 00000000 00400000 00000000 .P........@..... 3fe830344: 00000000 00000000 00000000 00000000 ................ # 将 bit21:20 置为 ’01‘,写回寄存器 root@thead-910:~# memtool mw -l 0x3fe830334 0x105014
注:如果提示没有 memtool 命令,使用
apt install -y memtool
安装。
GPIO 设备
通过 sysfs 方式控制 GPIO,先访问 /sys/class/gpio
目录,向 export 文件写入 GPIO 编号,使得该 GPIO 的操作接口从内核空间暴露到用户空间,GPIO 的操作接口包括 direction 和 value 等,direction 控制 GPIO 方向,而 value 可控制 GPIO 输出或获得 GPIO 输入。文件 IO 方式操作 GPIO,使用到了 4个函数 open、close、read、write。通过:ls -l /sys/class/gpio
可能以查看 GPIO的信息:
root@thead-910:~# ls -l /sys/class/gpio
total 0
--w------- 1 root root 4096 Jan 6 13:33 export
lrwxrwxrwx 1 root root 0 Dec 26 20:33 gpiochip352 -> ../../devices/platform/soc/3fff72000.gpio/gpio/gpiochip352
lrwxrwxrwx 1 root root 0 Dec 26 20:33 gpiochip384 -> ../../devices/platform/soc/3fff72000.gpio/gpio/gpiochip384
lrwxrwxrwx 1 root root 0 Dec 26 20:33 gpiochip416 -> ../../devices/platform/soc/3fff72000.gpio/gpio/gpiochip416
lrwxrwxrwx 1 root root 0 Dec 26 20:33 gpiochip448 -> ../../devices/platform/soc/3fff71000.gpio/gpio/gpiochip448
lrwxrwxrwx 1 root root 0 Dec 26 20:33 gpiochip480 -> ../../devices/platform/soc/3fff71000.gpio/gpio/gpiochip480
--w------- 1 root root 4096 Dec 26 20:33 unexport
文件说明:
- gpio_operation 通过 /sys/ 文件接口操作 IO 端口 GPIO 到文件系统的映射
- 控制 GPIO 的目录位于 /sys/class/gpio
- /sys/class/gpio/export 文件用于通知系统需要导出控制的 GPIO 引脚编号
- /sys/class/gpio/unexport 用于通知系统取消导出
- /sys/class/gpio/gpiochipXXX 目录保存系统中 GPIO 寄存器的信息,包括每个寄存器控制引脚的起始编号 base,寄存器名称,引脚总数
dts 中的定义分别为:
gpio0: gpio@3fff71000 {
compatible = "snps,dw-apb-gpio";
reg = <0x3 0xfff71000 0x0 0x1000>;
#address-cells = <1>;
#size-cells = <0>;
/* GPIO0[0-31] */
gpio0_porta: gpio0-controller@0 {
compatible = "snps,dw-apb-gpio-port";
gpio-controller;
#gpio-cells = <2>;
snps,nr-gpios = <32>;
reg = <0>;
interrupt-controller;
#interrupt-cells = <2>;
interrupt-parent = <&intc>;
interrupts = <27>;
};
/* GPIO0[32-63] */
gpio0_portb: gpio0-controller@1 {
compatible = "snps,dw-apb-gpio-port";
gpio-controller;
#gpio-cells = <2>;
snps,nr-gpios = <32>;
reg = <1>;
};
};
gpio1: gpio@3fff72000 {
compatible = "snps,dw-apb-gpio";
reg = <0x3 0xfff72000 0x0 0x1000>;
#address-cells = <1>;
#size-cells = <0>;
/* GPIO1[0-31] */
gpio1_porta: gpio1-controller@0 {
compatible = "snps,dw-apb-gpio-port";
gpio-controller;
#gpio-cells = <2>;
snps,nr-gpios = <32>;
reg = <0>;
};
/* GPIO1[32-63] */
gpio1_portb: gpio1-controller@1 {
compatible = "snps,dw-apb-gpio-port";
gpio-controller;
#gpio-cells = <2>;
snps,nr-gpios = <32>;
reg = <1>;
};
/* GPIO1[64-95] */
gpio1_portc: gpio1-controller@2 {
compatible = "snps,dw-apb-gpio-port";
gpio-controller;
#gpio-cells = <2>;
snps,nr-gpios = <32>;
reg = <2>;
};
};
gpiochipxxx 与 gpio 的对应关系:
gpiochip352:gpio1_portc,GPIO1[64-95] 分别对应为 352 ~ 383 号
gpiochip384:gpio1_portb,GPIO1[32-63] 分别对应为 384 ~ 415 号
gpiochip416:gpio1_porta,GPIO1[ 0-31] 分别对应为 416 ~ 447 号
gpiochip448:gpio0_portb,GPIO0[32-63] 分别对应为 448 ~ 479 号
gpiochip480:gpio0_porta,GPIO0[ 0-31] 分别对应为 480 ~ 511 号
GPIO 导出
需要对一个GPIO 控制,首先需要对 GPIO 进行导出,操作步骤如下:
第一步:确定 GPIO 引脚编号
引脚编号 = 控制引脚的寄存器基数 + 控制引脚寄存器位数
例如:(具体 GPIO 需要参考数据手册),如果使想用 SPI2_CLK(GPIO0_34),那么引脚编号就可能等于 448 + (34 - 32) = 450
第二步:导出引脚
向 /sys/class/gpio/export 写入此编号,比如 450 号引脚,在 shell 中可以通过以下命令实现:
# 导出 450号 GPIO
echo 450 > /sys/class/gpio/export
# 查看导出是否成功
ls /sys/class/gpio/gpio450
命令成功后生成 /sys/class/gpio/gpio450 目录,如果没有出现相应的目录,说明此引脚不可导出。
第三步:设置引导方向
通过 GPIO 的 direction文件,修改 GPIO 引脚的方向:
echo out > /sys/class/gpio/gpio450/direction
direction 接受的参数可以是:in、out、high、low。其中参数 high / low 在设置方向为输出的同时,将 value 设置为相应的 1 / 0。
第四步:设置或者查看 GPIO电平
GPIO 的 value 文件是端口的数值,为1或0:
# 设置 GPIO 电平
echo 1 > /sys/class/gpio/gpio450/value
# 查看 GPIO 电平
cat /sys/class/gpio/gpio450/value
第五步:取消GPIO 导出
echo 450 > /sys/class/gpio/unexport
编程示例
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h> //define O_WRONLY and O_RDONLY
static int write_file(const char *filename, const char *value)
{
int fd = open(filename, O_WRONLY);
if(fd != 0) {
write(fd, value,strlen(value));
close(fd);
return 0;
}
return EXIT_FAILURE;
}
// 打开 GPIO
static int gpio_open(char *id)
{
return write_file("/sys/class/gpio/export", id);
}
// 设置 GPIO 方向
static int gpio_direction(char *id, char *dir)
{
char filename[128];
sprintf(filename, "/sys/class/gpio/gpio%s/direction", id);
return write_file(filename, dir);
}
// 设置 GPIO 电平
static int gpio_set(char *id, char *dir)
{
char filename[128];
sprintf(filename, "/sys/class/gpio/gpio%s/value", id);
return write_file(filename, dir);
}
#define GPIO_PIN_450 "450"
int main(int argc, char **argv)
{
if (gpio_open(GPIO_PIN_450) == 0) {
gpio_direction(GPIO_PIN_450, "OUT");
while(1) {
gpio_set(GPIO_PIN_450, "1");
usleep(1000000);
gpio_set(GPIO_PIN_450, "0");
usleep(1000000);
}
}
return 0;
}
可以使用SHELL 脚本对GPIO的控制:
#!/bin/bash
echo 450 > /sys/class/gpio/export
echo out > /sys/class/gpio/gpio450/direction
while :
do
echo 1 > /sys/class/gpio/gpio450/value
sleep 1
echo 0 > /sys/class/gpio/gpio450/value
sleep 1
done
注:如果没有 sleep
命令,使用 apt install -y coreutils
安装。